package com.haotian.mail.client;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;

import com.haotian.mail.message._EmailSmtpResponseMessage;
import com.haotian.mail.uitl._MimeUtil;

/**
 * 
 * 邮件下发工具包
 * 
 * @author 张昊天<494009061@QQ.COM> <br>
 * 
 *         使用 javax.mail.internet.MimeMessage; 做为消息传输对象
 */
public class _EmailSMTPClient implements AutoCloseable {

	private Socket socket;

	private Logger log = Logger.getLogger(_EmailSMTPClient.class);
	private _EmailSmtpResponseMessage erm = new _EmailSmtpResponseMessage();
	private BufferedReader in;
	private BufferedWriter out;

	private Boolean auth = false;// 是否开启验证

	private String account;
	private String password;
	private String host;
	private int port = 0;

	private Boolean debug = false; // 开启客户端Debug 模式

	/**
	 * 静态内部类
	 * 
	 * _EmailSMTPClient 构建器
	 * 
	 * @author 张昊天
	 *
	 */
	public static class Builder {

		public Builder() {
		}

		private String account;
		private String password;
		private String host;
		private int port;
		private Boolean debug = false;
		private Boolean auth = false;// 是否开启验证

		/**
		 * 登录邮箱的用户名
		 * 
		 * @param account
		 * @return
		 */
		public Builder setAccount(String account) {
			this.account = account;
			return this;
		}

		/**
		 * 邮箱账户的密码
		 * 
		 * @param password
		 * @return
		 */
		public Builder setPassword(String password) {
			this.password = password;
			return this;
		}

		/**
		 * 邮件服务器地址
		 * 
		 * @param host
		 * @return
		 */
		public Builder setHost(String host) {
			this.host = host;
			return this;
		}

		/**
		 * 邮件服务器端口
		 * 
		 * @param port
		 * @return
		 */
		public Builder setPort(int port) {
			this.port = port;
			return this;
		}

		/**
		 * 是否开启 Debug 模式 打印所有交互信息
		 * 
		 * @param debug
		 * @return
		 */
		public Builder setDebug(Boolean debug) {
			this.debug = debug;
			return this;
		}

		/**
		 * 是否需要账户验证
		 * 
		 * @param auth
		 * @return
		 */
		public Builder setAuth(Boolean auth) {
			this.auth = auth;
			return this;
		}

		/**
		 * 创建 _EmailSMTPClient
		 * 
		 * @return
		 */
		public _EmailSMTPClient build() {
			return new _EmailSMTPClient(this);
		}

	}

	/**
	 * 利用构建器构建 ，易读性强
	 * 
	 * @param b
	 */
	private _EmailSMTPClient(Builder b) {
		this.debug = b.debug;
		this.host = b.host;
		this.account = b.account;
		this.password = b.password;
		this.port = b.port;
		this.auth = b.auth;
	}

	/**
	 * 验证连接是否可用 如果不可用 彻底关闭这个连接后 返回false
	 * 
	 * @return
	 * @throws IOException
	 */
	public boolean isConnection() throws IOException {

		if (socket != null && !socket.isClosed()) {
			try {
				this.sendServer("NOOP");
			} catch (Exception e) {
				if (this.debug)
					log.info("helo error ,Socket closed ");
				this.close();
				return false;
			}
			if (this.erm.getCode() != null && this.erm.getCode().equals("250")) {
				return true;
			} else {
				if (this.debug)
					log.info("server timeout closed");
				this.close();
				return false;
			}
		} else {
			if (this.debug)
				log.info("Socket closed");
			this.close();
			return false;
		}
	}

	/**
	 * 连接服务器
	 * 
	 * @return
	 * @throws IOException
	 */
	public final _EmailSmtpResponseMessage connection() throws IOException {

		if (null == debug)
			this.debug = false;
		if (debug) {
			log.info("Debug:" + host + " ,port:" + port + ",debug:" + debug);
		}

		socket = new Socket();
		InetSocketAddress address = new InetSocketAddress(host, port);
		socket.connect(address, 5000);

		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

		// 获取状态码
		getResult(in, "connection");

		return erm;
	}

	/**
	 * 重写 AutoCloseable 接口close 实现 try 的 自动关闭
	 */
	public final void close() throws IOException {

		if (socket != null && !socket.isClosed() && out != null) {

			this.sendServer("QUIT");

			this.socket.close();

		}
		this.socket = null;
		this.out = null;
		this.in = null;
	}

	/**
	 * 和邮件服务器握手
	 * 
	 * @param server
	 * @param in
	 * @param out
	 * @throws IOException
	 */
	public _EmailSmtpResponseMessage helo(String name) throws IOException {
		return sendServer("HELO " + name);
	}

	/**
	 * 用户验证
	 * 
	 * @return
	 * @throws IOException
	 */
	public _EmailSmtpResponseMessage authLogin() throws IOException {
		if (!this.auth) {
			erm.setCode("235");
			erm.setMessage("No validation required");
			return erm;
		}
		sendServer("AUTH LOGIN");
		if (erm.getCode() != null && erm.getCode().equals("334")) {
			this.sendServer(Base64.encodeBase64String(this.account.getBytes()));
			if (erm.getCode() != null && erm.getCode().equals("334")) {
				this.sendServer(Base64.encodeBase64String(this.password.getBytes()));
			}
		}
		return erm;
	}

	/**
	 * 设置邮件源地址
	 * 
	 * @param from
	 * @return
	 * @throws IOException
	 */
	private _EmailSmtpResponseMessage setMailFrom(String from) throws IOException {
		return sendServer("MAIL FROM:<" + from + ">");
	}

	/**
	 * 设置收件人
	 * 
	 * @param replyTo
	 * @return
	 * @throws IOException
	 */
	private _EmailSmtpResponseMessage setRcptTo(String replyTo) throws IOException {
		return sendServer("RCPT TO:<" + replyTo + ">");
	}

	/**
	 * 设置邮件体
	 * 
	 * @param mimeData
	 * @return
	 * @throws IOException
	 */
	private _EmailSmtpResponseMessage setData(String mimeData) throws IOException {

		if (this.debug) {
			log.debug(
					"===================================================================================================");
			log.debug(mimeData);
			log.debug(
					"===================================================================================================");
		}
		out.write(mimeData);
		out.write("\r\n");
		sendServer(".");
		return erm;
	}

	/**
	 * 下发邮件
	 * 
	 * @param message
	 *            使用javax.mail 的 MimeMessage
	 * @return
	 * @throws Exception
	 * @throws MessagingException
	 */

	public _EmailSmtpResponseMessage send(MimeMessage message) throws Exception, MessagingException {

		// 连接是否可用 如果不可用 重连 连接
		if (!this.isConnection()) {

			// 连接
			this.connection();

			if (this.erm.getCode() == null || !this.erm.getCode().equals("220"))
				return this.erm;
			this.helo(System.getProperty("os.name"));
			if (this.erm.getCode() == null || !this.erm.getCode().equals("250"))
				return erm;
			// login
			this.authLogin();
			if (this.erm.getCode() == null || !this.erm.getCode().equals("235"))
				return erm;
		}

		InternetAddress[] from = (InternetAddress[]) message.getFrom();
		this.setMailFrom(from[0].getAddress());
		if (this.erm.getCode() == null || !this.erm.getCode().equals("250")) {
			return erm;
		}

		Address[] recipients = message.getRecipients(Message.RecipientType.TO);
		for (Address to : recipients) {

			this.setRcptTo(((InternetAddress) to).getAddress());
			if (this.erm.getCode() == null || !this.erm.getCode().equals("250")) {
				return erm;
			}
		}

		// 请求写数据
		sendServer("DATA");
		if (this.erm.getCode() == null || !this.erm.getCode().equals("354")) {
			return erm;
		}

		// 开始写数据
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		message.saveChanges();
		message.writeTo(baos);
		byte[] buf = baos.toByteArray();
		baos.close();
		message = null;
		this.setData(new String(buf));
		if (this.erm.getCode() == null || !this.erm.getCode().equals("250")) {
			return erm;
		}
		return erm;
	}

	/**
	 * @Title: sendText
	 * @Description: 以文本形式发送邮件
	 * @param Title
	 *            邮件标题
	 * @param Text
	 *            邮件内容
	 * @param charset
	 *            文本编码
	 * @return boolean 返回类型
	 * @author 494009061@qq.com
	 * @throws Exception
	 * @date 2016年11月22日 上午11:28:07
	 */
	@SuppressWarnings("deprecation")
	public _EmailSmtpResponseMessage sendText(String fromUser, String sendToUse, String ccUser, String bccUser,
			String title, String Text, String charset) throws Exception {
		send(_MimeUtil.createTextMime(fromUser, sendToUse, ccUser, bccUser, title, Text, charset));
		return erm;
	}

	/**
	 * 
	 * @Title: sendHTML
	 * @Description: 以网页形式发送邮件
	 * @param content
	 *            网页内容
	 * @param charset
	 *            文本编码
	 * @return boolean 返回类型
	 * @author 494009061@qq.com
	 * @return
	 * @throws Exception
	 * @date 2016年11月22日 上午11:29:10
	 */
	@SuppressWarnings("deprecation")
	public _EmailSmtpResponseMessage sendHTML(String fromUser, String sendToUse, String ccUser, String bccUser,
			String title, String content, String charset) throws Exception {
		send(_MimeUtil.createHtmlMime(fromUser, sendToUse, ccUser, bccUser, title, content, charset));
		return erm;
	}

	/**
	 * 写一行命令后发出去
	 * 
	 * @param cmd
	 * @return
	 * @throws IOException
	 */
	private _EmailSmtpResponseMessage sendServer(String cmd) throws IOException {
		// 输出流是否停止 true 为已经停止
		if (!socket.isOutputShutdown() && out != null) {
			try {
				out.write(cmd);
				out.write("\r\n");
				out.flush();
			} catch (SocketException e) {
				erm.setCode(null);
				erm.setMessage(null);
				if (this.debug)
					log.info("输出流关闭");
				return erm;
			}
		} else {
			erm.setCode(null);
			erm.setMessage(null);
			if (this.debug)
				log.info("输出流关闭");
			return erm;
		}

		// 命令发送时间
		erm.setSendTime(System.currentTimeMillis());

		if (debug)
			log.info("S:" + cmd + ",T:" + erm.getSendTime());

		this.getResult(in, cmd);

		return erm;
	}

	/**
	 * 获取服务器返回结果后刷新本类中 _EmailSmtpResponseMessage 对象
	 * 
	 * @param in
	 * @throws IOException
	 */
	private void getResult(BufferedReader in, String cmd) throws IOException {

		// 输入流是否可用
		if (!socket.isInputShutdown()) {

			String line = "";
			line = in.readLine();
			erm.setReplyTime(System.currentTimeMillis());
			if (debug) {
				log.info("R:" + line + " ,T:" + erm.getReplyTime());
			}

			if (line == null) {
				if (debug)
					log.info("write===============" + cmd + ",SMTP response null ");
				erm.setCode(null);
				erm.setMessage(null);
			} else {
				// 从服务器返回消息中读出状态码
				erm.setCode(line.substring(0, 3));
				erm.setMessage(line.substring(4));
			}
		} else {
			if (debug)
				log.info("write===============" + cmd + ",输入流已经关闭 ");
			erm.setCode(null);
			erm.setMessage(null);
		}

	}

//	public static void main(String[] args) throws Exception {
//
//		_EmailSMTPClient client = new _EmailSMTPClient.Builder().setAccount("18310451350@139.com").setAuth(true)
//				.setDebug(true).setHost("smtp.139.com").setPassword("fendou230184").setPort(25).build();
//
//		MimeMessage newMimeMessage = _MimeUtil.newMimeMessage("18310451350@139.com", "15201509073@139.com", null, null,
//				null);
//		newMimeMessage.setSubject("我是测试朱润华123");
//		newMimeMessage.setText("123123");
//		_MimeUtil.setFileToMimeMessage(newMimeMessage, new File[]{new File("c://list.pdf")});
//		ByteArrayOutputStream baos = new ByteArrayOutputStream();
//		newMimeMessage.saveChanges();
//		newMimeMessage.writeTo(baos);
//
//		Map<String, Object> jsonp = new HashMap<>();
//		jsonp.put("text", baos.toByteArray());
//		String jsonString = JSON.toJSONString(jsonp);
//
//		_RedisConfig config = new _RedisConfig();
//		config.setMaxTotal(2);
//		Set<HostAndPort> nodes = new HashSet<>();
//		nodes.add(new HostAndPort("127.0.0.1", 6141));
//		_RedisQueue queue = new _RedisQueue(nodes, "123456", 3000, config);
//		queue.lpush("aaa", jsonString);
//
//		String brpop = queue.brpop("aaa");
//		JSONObject parseObject = JSON.parseObject(brpop);
//		byte[] text = parseObject.getBytes("text");
//		ByteArrayInputStream bais = new ByteArrayInputStream(text);
//		MimeMessage mime2 = _MimeUtil.newMimeMessage(null, null, null, null, bais);
//		client.send(mime2);
//
//	}

}
